#include "qemu/osdep.h"
#include "hw/misc/localbus.h"
#include "hw/irq.h"
#include "hw/qdev-properties.h"
#include "sysemu/runstate.h"
#include "qemu/bitops.h"
#include "hw/sysbus.h"
#include "migration/vmstate.h"
#include "hw/arm/primecell.h"
#include "qemu/log.h"
#include "qemu/module.h"
#include "qapi/error.h"
#include "qemu/error-report.h"
#include "exec/address-spaces.h"
#include "hw/misc/hs2101.h"

static void hs2101_realize(DeviceState *d, Error **errp)
{
}

static void hs2101_reset(DeviceState *d)
{
}

static void lbc_cs_memory_map(hs2101_state *s)
{
    hs2101_regs *regs = &s->regs;
    
    int cs;
    for (cs = 0; cs < LBC_CS_NUM; cs++) {
        struct lbc_iomem_cs_file_s *f = &s->cs_file[cs];
        if (f->base_addr == 0 || f->addr_size == 0) {
            continue;
        }
        memory_region_init(&f->container, NULL, "lbc-gpmc-file", f->addr_size);
        memory_region_add_subregion(&f->container, 0, &f->cs_iomem);
        memory_region_add_subregion(get_system_memory(), f->base_addr, &f->container);
        f->is_cs_mapping = true;
    }
}

static void lbc_cs_memory_unmap(hs2101_state *s)
{
    int cs;
    for (cs = 0; cs < LBC_CS_NUM; cs++) {
        struct lbc_iomem_cs_file_s *f = &s->cs_file[cs];
        if (f->base_addr == 0 || f->addr_size == 0 || f->is_cs_mapping == false) {
            continue;
        }
        memory_region_del_subregion(get_system_memory(), &f->container);
        memory_region_del_subregion(&f->container, &f->cs_iomem);
        object_unparent(OBJECT(&f->container));
        f->is_cs_mapping = false;
    }
}

static Property hs2101_properties[] = {
    DEFINE_PROP_END_OF_LIST(),
};

static uint64_t localbus_read_part0(hwaddr offset, hs2101_state *s)
{
    hs2101_regs *regs = &(s->regs);
    uint32_t ret = -1;
    
    switch (offset) {
        case LB_CS0_BASE_ADDR:
            return regs->cs0_base_addr;
        case LB_CS1_BASE_ADDR:
            return regs->cs1_base_addr;
        case LB_CS2_BASE_ADDR:
            return regs->cs2_base_addr;
        case LB_CS3_BASE_ADDR:
            return regs->cs3_base_addr;
        case LB_CS0_CTRL:
            return regs->cs0_ctrl;
        case LB_CS1_CTRL:
            return regs->cs1_ctrl;
        case LB_CS2_CTRL:
            return regs->cs2_ctrl;
        case LB_CS3_CTRL:
            return regs->cs3_ctrl;
        case LB_CS0_T_SET0:
            return regs->cs0_t_set0;
        case LB_CS0_T_SET1:
            return regs->cs0_t_set1;
        case LB_CS0_T_SET2:
            return regs->cs0_t_set2;
        case LB_CS1_T_SET0:
            return regs->cs1_t_set0;
        case LB_CS1_T_SET1:
            return regs->cs1_t_set1;
        case LB_CS1_T_SET2:
            return regs->cs1_t_set2;
        case LB_CS2_T_SET0:
            return regs->cs2_t_set0;
        case LB_CS2_T_SET1:
            return regs->cs2_t_set1;
        case LB_CS2_T_SET2:
            return regs->cs2_t_set2;
        case LB_CS3_T_SET0:
            return regs->cs3_t_set0;
        case LB_CS3_T_SET1:
            return regs->cs3_t_set1;
        case LB_CS3_T_SET2:
            return regs->cs3_t_set2;
    }

    return ret;
}

static uint64_t localbus_read_part1(hwaddr offset, hs2101_state *s)
{
    hs2101_regs *regs = &(s->regs);
    uint32_t ret = -1;
    
    switch (offset) {
        case LB_INTMSK:
            return regs->initmsk;
        case LB_INTSTS:
            return regs->initsts;
        case LB_RINT:
            return regs->rint;
        case LB_RDY_TOUT_0:
            return regs->rdy_tout_0;
        case LB_RDY_TOUT_1:
            return regs->rdy_tout_1;
        case LB_RDY_TOUT_2:
            return regs->rdy_tout_2;
        case LB_RDY_TOUT_3:
            return regs->rdy_tout_3;
        case LB_CTRL:
            return regs->total_ctrl;
        case LB_PROT_ADDR_START_0:
            return regs->prot_addr_start_0;
        case LB_PROT_ADDR_START_1:
            return regs->prot_addr_start_1;
        case LB_PROT_ADDR_START_2:
            return regs->prot_addr_start_2;
        case LB_PROT_ADDR_START_3:
            return regs->prot_addr_start_3;
        case LB_PROT_ADDR_START_4:
            return regs->prot_addr_start_4;
        case LB_PROT_ADDR_START_5:
            return regs->prot_addr_start_5;
        case LB_PROT_ADDR_START_6:
            return regs->prot_addr_start_6;
        case LB_PROT_ADDR_START_7:
            return regs->prot_addr_start_7;
        case LB_PROT_ADDR_OVER_0:
            return regs->prot_addr_over_0;
        case LB_PROT_ADDR_OVER_1:
            return regs->prot_addr_over_1;
        case LB_PROT_ADDR_OVER_2:
            return regs->prot_addr_over_2;
        case LB_PROT_ADDR_OVER_3:
            return regs->prot_addr_over_3;
        case LB_PROT_ADDR_OVER_4:
            return regs->prot_addr_over_4;
        case LB_PROT_ADDR_OVER_5:
            return regs->prot_addr_over_5;
        case LB_PROT_ADDR_OVER_6:
            return regs->prot_addr_over_6;
        case LB_PROT_ADDR_OVER_7:
            return regs->prot_addr_over_7;
    }

    return ret;
}

static uint64_t localbus_read_part2(hwaddr offset, hs2101_state *s)
{
    hs2101_regs *regs = &(s->regs);
    uint32_t ret = -1;
    
    switch (offset) {
        case LB_NOR_CMD0_ADDR:
            return regs->nor_cmd0_addr;
        case LB_NOR_CMD0_MASK:
            return regs->nor_cmd0_mask;
        case LB_NOR_CMD1_ADDR:
            return regs->nor_cmd1_addr;
        case LB_NOR_CMD1_MASK:
            return regs->nor_cmd1_mask;
        case LB_NOR_CMD2_ADDR:
            return regs->nor_cmd2_addr;
        case LB_NOR_CMD2_MASK:
            return regs->nor_cmd2_mask;
        case LB_NOR_CMD3_ADDR:
            return regs->nor_cmd3_addr;
        case LB_NOR_CMD3_MASK:
            return regs->nor_cmd3_mask;
        case LB_HL_PROT_KEY0:
            return regs->hl_port_key0;
        case LB_HL_PROT_KEY1:
            return regs->hl_port_key1;
        case LB_HL_PROT_KEY2:
            return regs->hl_port_key2;
        case LB_HL_PROT_KEY3:
            return regs->hl_port_key3;
        case DBG_ST0:
            return regs->dbg_st0;
        case DBG_OVER_TIME_ADDR:
            return regs->dbg_over_time_addr;
        case DBG_T_CFG:
            return regs->dbg_t_cfg;
        case CF_CARD_ST:
            return regs->cf_card_st;
        case LB_CFG_T_UPDATE_EN:
            return regs->cfg_t_update_en;
        case WP_RSP_ERR_EN:
            return regs->wp_rsp_err_en;
    }

    return ret;
}

static uint64_t hs2101_read(void *opaque, hwaddr offset, unsigned size)
{
    hs2101_state *s = (hs2101_state *)opaque;
    uint32_t ret = 0;
    
    if ((offset >= LB_CS0_BASE_ADDR) && (offset <= LB_CS3_T_SET2)) {
        ret = localbus_read_part0(offset, s);
    } else if ((offset >= LB_INTMSK) && (offset <= LB_PROT_ADDR_OVER_7)) {
        ret = localbus_read_part1(offset, s);
    } else if ((offset >= LB_NOR_CMD0_ADDR) && (offset <= WP_RSP_ERR_EN)) {
        ret = localbus_read_part2(offset, s);
    } else {
        ret = -1;
    }

    return ret;
}

static uint64_t localbus_write_part0(hwaddr offset, uint64_t val, hs2101_state *s)
{
    hs2101_regs *regs = &(s->regs);
    
    switch (offset) {
        case LB_CS0_BASE_ADDR:
            break;
        case LB_CS1_BASE_ADDR:
            break;
        case LB_CS2_BASE_ADDR:
            break;
        case LB_CS3_BASE_ADDR:
            break;
        case LB_CS0_CTRL:
            regs->cs0_ctrl = val;
            break;
        case LB_CS1_CTRL:
            regs->cs1_ctrl = val;
            break;
        case LB_CS2_CTRL:
            regs->cs2_ctrl = val;
            break;
        case LB_CS3_CTRL:
            regs->cs3_ctrl = val;
            break;
        case LB_CS0_T_SET0:
            regs->cs0_t_set0 = val;
            break;
        case LB_CS0_T_SET1:
            regs->cs0_t_set1 = val;
            break;
        case LB_CS0_T_SET2:
            regs->cs0_t_set2 = val;
            break;
        case LB_CS1_T_SET0:
            regs->cs1_t_set0 = val;
            break;
        case LB_CS1_T_SET1:
            regs->cs1_t_set1 = val;
            break;
        case LB_CS1_T_SET2:
            regs->cs1_t_set2 = val;
            break;
        case LB_CS2_T_SET0:
            regs->cs2_t_set0 = val;
            break;
        case LB_CS2_T_SET1:
            regs->cs2_t_set1 = val;
            break;
        case LB_CS2_T_SET2:
            regs->cs2_t_set2 = val;
            break;
        case LB_CS3_T_SET0:
            regs->cs3_t_set0 = val;
            break;
        case LB_CS3_T_SET1:
            regs->cs3_t_set1 = val;
            break;
        case LB_CS3_T_SET2:
            regs->cs3_t_set2 = val;
            break;
    }
}

static uint64_t localbus_write_part1(hwaddr offset, uint64_t val, hs2101_state *s)
{
    hs2101_regs *regs = &(s->regs);
    
    switch (offset) {
        case LB_INTMSK:
            regs->initmsk = val;
            break;
        case LB_INTSTS:
            regs->initsts = val;
            break;
        case LB_RINT:
            regs->rint = val;
            break;
        case LB_RDY_TOUT_0:
            regs->rdy_tout_0 = val;
            break;
        case LB_RDY_TOUT_1:
            regs->rdy_tout_1 = val;
            break;
        case LB_RDY_TOUT_2:
            regs->rdy_tout_2 = val;
            break;
        case LB_RDY_TOUT_3:
            regs->rdy_tout_3 = val;
            break;
        case LB_CTRL:
            regs->total_ctrl = val;
            break;
        case LB_PROT_ADDR_START_0:
            regs->prot_addr_start_0 = val;
            break;
        case LB_PROT_ADDR_START_1:
            regs->prot_addr_start_1 = val;
            break;
        case LB_PROT_ADDR_START_2:
            regs->prot_addr_start_2 = val;
            break;
        case LB_PROT_ADDR_START_3:
            regs->prot_addr_start_3 = val;
            break;
        case LB_PROT_ADDR_START_4:
            regs->prot_addr_start_4 = val;
            break;
        case LB_PROT_ADDR_START_5:
            regs->prot_addr_start_5 = val;
            break;
        case LB_PROT_ADDR_START_6:
            regs->prot_addr_start_6 = val;
            break;
        case LB_PROT_ADDR_START_7:
            regs->prot_addr_start_7 = val;
            break;
        case LB_PROT_ADDR_OVER_0:
            regs->prot_addr_over_0 = val;
            break;
        case LB_PROT_ADDR_OVER_1:
            regs->prot_addr_over_1 = val;
            break;
        case LB_PROT_ADDR_OVER_2:
            regs->prot_addr_over_2 = val;
            break;
        case LB_PROT_ADDR_OVER_3:
            regs->prot_addr_over_3 = val;
            break;
        case LB_PROT_ADDR_OVER_4:
            regs->prot_addr_over_4 = val;
            break;
        case LB_PROT_ADDR_OVER_5:
            regs->prot_addr_over_5 = val;
            break;
        case LB_PROT_ADDR_OVER_6:
            regs->prot_addr_over_6 = val;
            break;
        case LB_PROT_ADDR_OVER_7:
            regs->prot_addr_over_7 = val;
            break;
    }
}

static uint64_t localbus_write_part2(hwaddr offset, uint64_t val, hs2101_state *s)
{
    hs2101_regs *regs = &(s->regs);
    uint32_t ret = 0;
    
    switch (offset) {
        case LB_NOR_CMD0_ADDR:
            regs->nor_cmd0_addr = val;
            break;
        case LB_NOR_CMD0_MASK:
            regs->nor_cmd0_mask = val;
            break;
        case LB_NOR_CMD1_ADDR:
            regs->nor_cmd1_addr = val;
            break;
        case LB_NOR_CMD1_MASK:
            regs->nor_cmd1_mask = val;
            break;
        case LB_NOR_CMD2_ADDR:
            regs->nor_cmd2_addr = val;
            break;
        case LB_NOR_CMD2_MASK:
            regs->nor_cmd2_mask = val;
            break;
        case LB_NOR_CMD3_ADDR:
            regs->nor_cmd3_addr = val;
            break;
        case LB_NOR_CMD3_MASK:
            regs->nor_cmd3_mask = val;
            break;
        case LB_HL_PROT_KEY0:
            regs->hl_port_key0 = val;
            break;
        case LB_HL_PROT_KEY1:
            regs->hl_port_key1=  val;
            break;
        case LB_HL_PROT_KEY2:
            regs->hl_port_key2 = val;
            break;
        case LB_HL_PROT_KEY3:
            regs->hl_port_key3 = val;
            break;
        case DBG_ST0:
            regs->dbg_st0 = val;
            break;
        case DBG_OVER_TIME_ADDR:
            regs->dbg_over_time_addr = val;
            break;
        case DBG_T_CFG:
            regs->dbg_t_cfg = val;
            break;
        case CF_CARD_ST:
            regs->cf_card_st = val;
            break;
        case LB_CFG_T_UPDATE_EN:
            if (val == 0x1) {
                lbc_cs_memory_unmap(s);
                lbc_cs_memory_map(s);
            }
            break;
        case WP_RSP_ERR_EN:
            regs->wp_rsp_err_en = val;
            break;
        default:
            break;
    }
}

static void hs2101_write(void *opaque, hwaddr offset, uint64_t val, unsigned size)
{
    hs2101_state *s = (hs2101_state *)opaque;
    hs2101_regs *regs = &(s->regs);

    if ((offset >= LB_CS0_BASE_ADDR) && (offset <= LB_CS3_T_SET2)) {
        localbus_write_part0(offset, val, s);
    } else if ((offset >= LB_INTMSK) && (offset <= LB_PROT_ADDR_OVER_7)) {
        localbus_write_part1(offset, val, s);
    } else if ((offset >= LB_NOR_CMD0_ADDR) && (offset <= WP_RSP_ERR_EN)) {
        localbus_write_part2(offset, val, s);
    } else {
        DPRINTF("[i2c write reg]:offset:0x%x is false\n", offset);
    }
}

static uint64_t lbc_cs_iomem_read(void *opaque, hwaddr addr, unsigned size)
{
    struct lbc_iomem_cs_file_s *f = (struct lbc_iomem_cs_file_s *)opaque;
    return localbus_recv(f->bus, addr);
}

static void lbc_cs_iomem_write(void *opaque, hwaddr addr, uint64_t value, unsigned size)
{
    struct lbc_iomem_cs_file_s *f = (struct lbc_iomem_cs_file_s *)opaque;
    localbus_send(f->bus, addr, (uint32_t) value);
}

static const MemoryRegionOps hs2101_ops = {
    .read = hs2101_read,
    .write = hs2101_write,
    .endianness = DEVICE_NATIVE_ENDIAN,
};

static const MemoryRegionOps lbc_cs_iomem_ops = {
    .read = lbc_cs_iomem_read,
    .write = lbc_cs_iomem_write,
    .endianness = DEVICE_NATIVE_ENDIAN,
};

static const VMStateDescription vmstate_hs2101 = {
    .name = TYPE_HS2101,
    .version_id = 1,
    .minimum_version_id = 1,
    .fields = (VMStateField[]) {
        VMSTATE_END_OF_LIST()
    }
};

static void hs2101_class_init(ObjectClass *klass, void *data)
{
    DeviceClass *dc = DEVICE_CLASS(klass);
    dc->realize = hs2101_realize;
    dc->reset = hs2101_reset;
    dc->vmsd = &vmstate_hs2101;
    device_class_set_props(dc, hs2101_properties);
}

static void hs2101_finalize(Object *obj)
{
}

static void hs2101_init(Object *obj)
{
    DeviceState *dev = DEVICE(obj);
    SysBusDevice *sd = SYS_BUS_DEVICE(obj);
    hs2101_state *s = HS2101(obj);

    s->bus = localbus_init_bus(dev, "localbus");

    hs2101_regs *regs = &(s->regs);
    memset(regs, sizeof(hs2101_regs), sizeof(hs2101_regs));
    regs->cs0_base_addr = 0x30000000;
    regs->cs1_base_addr = 0x40000000;
    regs->cs2_base_addr = 0x50000000;
    regs->cs3_base_addr = 0x58000000;
    regs->cs0_ctrl = 0xc02d;
    regs->cs1_ctrl = 0xc02d;
    regs->cs2_ctrl = 0xc02c;
    regs->cs3_ctrl = 0xc02c;
    regs->cs0_t_set0 = 0x10805008;
    regs->cs0_t_set1 = 0x08201420;
    regs->cs0_t_set2 = 0x20840880;
    regs->cs1_t_set0 = 0x10805008;
    regs->cs1_t_set1 = 0x08201420;
    regs->cs1_t_set2 = 0x20840880;
    regs->cs2_t_set0 = 0x10805008;
    regs->cs2_t_set1 = 0x08201420;
    regs->cs2_t_set2 = 0x20840880;
    regs->cs3_t_set0 = 0x10805008;
    regs->cs3_t_set1 = 0x08201420;
    regs->cs3_t_set2 = 0x20840880;
    regs->initmsk = 0x0000;
    regs->initsts = 0x0000;
    regs->rint = 0x0000;
    regs->rdy_tout_0 = 0xffffffff;
    regs->rdy_tout_1 = 0xffffffff;
    regs->rdy_tout_2 = 0xffffffff;
    regs->rdy_tout_3 = 0xffffffff;
    regs->total_ctrl = 0x0000;
    regs->prot_addr_start_0 = 0x0000;
    regs->prot_addr_over_0 = 0x0000;
    regs->prot_addr_start_1 = 0x0000;
    regs->prot_addr_over_1 = 0x0000;
    regs->prot_addr_start_2 = 0x0000;
    regs->prot_addr_over_2 = 0x0000;
    regs->prot_addr_start_3 = 0x0000;
    regs->prot_addr_over_3 = 0x0000;
    regs->prot_addr_start_4 = 0x0000;
    regs->prot_addr_over_4 = 0x0000;
    regs->prot_addr_start_5 = 0x0000;
    regs->prot_addr_over_5 = 0x0000;
    regs->prot_addr_start_6 = 0x0000;
    regs->prot_addr_over_6 = 0x0000;
    regs->prot_addr_start_7 = 0x0000;
    regs->prot_addr_over_7 = 0x0000;
    regs->nor_cmd0_addr = 0x0000;
    regs->nor_cmd0_mask = 0xffffffff;
    regs->nor_cmd1_addr = 0x0000;
    regs->nor_cmd1_mask = 0xffffffff;
    regs->nor_cmd2_addr = 0x0000;
    regs->nor_cmd2_mask = 0xffffffff;
    regs->nor_cmd3_addr = 0x0000;
    regs->nor_cmd3_mask = 0xffffffff;
    regs->hl_port_key0 = 0x69966996;
    regs->hl_port_key1 = 0x69966996;
    regs->hl_port_key2 = 0x69966996;
    regs->hl_port_key3 = 0x69966996;
    regs->dbg_st0 = 0x0000;
    regs->dbg_over_time_addr = 0x0000;
    regs->dbg_t_cfg = 0x0000;
    regs->cf_card_st = 0x0000;
    regs->cfg_t_update_en = 0x0000;
    regs->wp_rsp_err_en = 0x0000;

    memory_region_init_io(&s->iomem, OBJECT(dev), &hs2101_ops, s, TYPE_HS2101, HS2101_MMIO_SIZE);
    sysbus_init_mmio(sd, &s->iomem);
    sysbus_init_irq(sd, &s->irq);

    uint32_t cs_mem_size[LBC_CS_NUM] = {0x10000000, 0x10000000, 0x8000000, 0x8000000};
    uint32_t cs_base_addr[LBC_CS_NUM] = {regs->cs0_base_addr, regs->cs1_base_addr, regs->cs2_base_addr, regs->cs3_base_addr};

    int cs;
    for (cs = 0; cs < LBC_CS_NUM; cs++) {
        char iomem_name[64];
        s->cs_file[cs].bus = s->bus;
        s->cs_file[cs].base_addr = cs_base_addr[cs];
        s->cs_file[cs].addr_size = cs_mem_size[cs];
        sprintf(iomem_name, "lbc-cs%d-dev", cs);
        memory_region_init_io(&s->cs_file[cs].cs_iomem, NULL, &lbc_cs_iomem_ops, &s->cs_file[cs], iomem_name, cs_mem_size[cs]);
    }
}

static const TypeInfo hs2101_info = {
    .name = TYPE_HS2101,
    .parent = TYPE_SYS_BUS_DEVICE,
    .instance_size = sizeof(hs2101_state),
    .instance_init = hs2101_init,
    .instance_finalize = hs2101_finalize,
    .class_init = hs2101_class_init,
};

static void hs2101_register_types(void)
{
    type_register_static(&hs2101_info);
}

type_init(hs2101_register_types)